package com.hdu.kdk_es_orm.service;

import com.google.common.collect.ImmutableList;
import com.hdu.kdk_es_orm.core.EsQueryBuilderBuildHelper;
import com.hdu.kdk_es_orm.wrapper.EsWrapper;
import lombok.val;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.core.CountRequest;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

import static com.alibaba.fastjson.JSON.parseObject;
import static com.alibaba.fastjson.JSON.toJSON;
import static com.hdu.kdk_es_orm.config.EsConfig.getEsClient;
import static com.hdu.kdk_es_orm.core.EsQueryBuilderBuildHelper.convertToQueryBuilder;
import static org.elasticsearch.search.sort.SortOrder.ASC;
import static org.elasticsearch.search.sort.SortOrder.DESC;


/**
 * es crud 底层
 */
public class EsService {

    private static final Logger logger = LoggerFactory.getLogger(EsService.class);

    public static <T> boolean batchSave(List<T> documents, List<String> docIds, String index) {
        if (documents == null || docIds == null || documents.size() != docIds.size()) {
            logger.error("Documents or docIds are null or their sizes don't match.");
            return false;
        }

        BulkRequest bulkRequests = new BulkRequest();
        for (int i = 0; i < docIds.size(); i++) {
            String docId = docIds.get(i);
            T curDocument = documents.get(i);
            bulkRequests.add(new IndexRequest(index)
                    .id(docId)
                    .source(getXContentBuilder(curDocument, curDocument.getClass())));
        }

        try {
            BulkResponse bulkResponse = getEsClient().bulk(bulkRequests, RequestOptions.DEFAULT);
            return !bulkResponse.hasFailures();
        } catch (Exception e) {
            logger.error("Failed to batch save documents to Elasticsearch", e);
            return false;
        }
    }

    public static boolean delete(String docId, String index) {
        if (docId == null || index == null) {
            logger.error("docId or index is null.");
            return false;
        }

        DeleteRequest deleteRequest = new DeleteRequest(index, docId);

        try {
            getEsClient().delete(deleteRequest, RequestOptions.DEFAULT);
            return true;
        } catch (Exception e) {
            logger.error("Failed to delete document from Elasticsearch", e);
            return false;
        }
    }

    public static boolean update(Object document, String docId, String index) {
        if (document == null || docId == null || index == null) {
            logger.error("Document, docId, or index is null.");
            return false;
        }

        UpdateRequest updateRequest = new UpdateRequest(index, docId);
        updateRequest.doc(toJSON(document));

        try {
            getEsClient().update(updateRequest, RequestOptions.DEFAULT);
            return true;
        } catch (Exception e) {
            logger.error("Failed to update document in Elasticsearch", e);
            return false;
        }
    }

    public static boolean saveOrUpdate(Object document, Object docId, String index) {
        val client = getEsClient();

        try {
            // 验证docId是否合法（示例，需根据实际情况调整）
            if (docId == null || docId.toString().isEmpty()) {
                logger.error("Invalid docId provided.");
                return false;
            }

            val request = new UpdateRequest(index, docId.toString());
            val indexRequest = new IndexRequest(index, docId.toString());
            indexRequest.source(toJSON(document), XContentType.JSON);
            request.doc(indexRequest);
            request.docAsUpsert(true);

            val response = client.update(request, RequestOptions.DEFAULT);
            // 确认操作成功
            if (response.getResult() == UpdateResponse.Result.UPDATED) {
                return true;
            } else {
                logger.error("Document update failed for unknown reasons.");
                return false;
            }
        } catch (IOException e) {
            logger.error("IOException caught during document update", e);
            return false;
        } catch (Exception e) {
            logger.error("Unexpected exception caught during document update", e);
            return false;
        }
    }

    public static <T> List<T> queryList(EsWrapper<T> esWrapper,
                                        String... indices) {

        // 检查边界条件
        validateParameters(esWrapper);

        val queryBuilder = convertToQueryBuilder(esWrapper.getQueryFilter());

        val searchSourceBuilder = createSearchSourceBuilder(esWrapper, queryBuilder);

        val searchRequest = new SearchRequest(indices);
        searchRequest.source(searchSourceBuilder);

        List<T> res = new ArrayList<>();
        try {
            val esClient = getEsClient();
            val response = esClient.search(searchRequest, RequestOptions.DEFAULT);

            handleSearchResponse(response, esWrapper, res);

        } catch (Exception e) {
            logger.error("查询Elasticsearch失败", e); // 记录异常日志
        }
        return res;
    }

    private static SearchSourceBuilder createSearchSourceBuilder(EsWrapper<?> esWrapper, QueryBuilder queryBuilder) {
        val searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(queryBuilder);
        searchSourceBuilder.from(esWrapper.getFrom());
        searchSourceBuilder.size(esWrapper.getSize());
        if (esWrapper.getIncludeFields().length > 0) {
            searchSourceBuilder.fetchSource(esWrapper.getIncludeFields(), new String[0]);
        }
        esWrapper.getOrderField()
                .ifPresent(oF -> {
                    SortOrder sortOrder = esWrapper.getOrderType() == EsWrapper.OrderType.ASC ? ASC : DESC;
                    FieldSortBuilder sortBuilder = new FieldSortBuilder(oF).order(sortOrder);
                    searchSourceBuilder.sort(sortBuilder);
                });
        return searchSourceBuilder;
    }

    private static void validateParameters(EsWrapper<?> esWrapper) {
        if (esWrapper.getFrom() < 0 || esWrapper.getSize() < 0) {
            throw new IllegalArgumentException("from和size参数不能为负数");
        }
    }

    private static <T> void handleSearchResponse(SearchResponse response, EsWrapper<T> esWrapper, List<T> res) {
        val hits = response.getHits();
        for (SearchHit hit : hits) {
            val singleRes = parseObject(hit.getSourceAsString(), esWrapper.getCLAZZ());
            res.add(singleRes);
        }
    }

    private static XContentBuilder getXContentBuilder(Object document, Class<?> entityClass) {

        XContentBuilder xContentBuilder;
        try {
            xContentBuilder = XContentFactory.jsonBuilder()
                    .startObject();

            for (Field field : entityClass.getDeclaredFields()) {
                field.setAccessible(true);
                if (Objects.nonNull(field.get(document))) {
                    xContentBuilder.field(field.getName(), field.get(document));
                }
            }

            xContentBuilder.endObject();

        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        return xContentBuilder;
    }

    public static <T> Long count(EsWrapper<T> esWrapper, String indexName) {
        val queryBuilder = convertToQueryBuilder(esWrapper.getQueryFilter());
        val countRequest = new CountRequest(new String[]{indexName}, queryBuilder);
        val esClient = getEsClient();
        try {
            val response = esClient.count(countRequest, RequestOptions.DEFAULT);
            return response.getCount();
        } catch (IOException e) {
            logger.error("EsService.count() fail, indexName : {}, searchSource : {}", indexName, countRequest, e);
            return 0L;
        }
    }
}
